
#include <vector>
#include <apr-1/apr_date.h>
#include "../crypt/RSAPublicKey.h"
#include "../crypt/RSAPrivateKey.h"
#include "../crypt/AESSymmetricKey.h"
#include "../iface/APRInterface.h"
#include "../utils/StringUtils.h"
#include "KeyTableView.h"
#include "NewKeyDB.h"

/** Constructs the view for this key table.
 * 
 * @param newkdb
 * The key database pointer that points to the key database to be connected to.
 * 
 * @param ikeytableusetype
 * A flag denoting the use scenario for keys in this table.
 * One of KeyTableView::keytableusetype.
 * 
 * @param itablename
 * The name of the table that this class is to be a view of.
 * 
 * @param itableWIPpwd
 * The work-in-progress password to be used for reading and writing secret keys
 * to this table.
 * 
 * @param iresidentkeyname
 * The name of the key(s) to be used to identify the main actor.
 * 
 * @param iresidentkeypwd
 * The password to be used to decrypt the key of the main actor. */
KeyTableView::KeyTableView(NewKeyDB *newkdb, int ikeytableusetype,
   const char *itablename, const char *itableWIPpwd,
   const char *iresidentkeyname, const char *iresidentkeypwd)
{
   keytableusetype = ikeytableusetype;
   tablename = (itablename) ? strdup(itablename) : NULL;
   tableWIPpwd = (itableWIPpwd) ? strdup(itableWIPpwd) : NULL;
   residentkeyname = (iresidentkeyname) ? strdup(iresidentkeyname) : NULL;
   ownkey = NULL;
   int errval = tryToGetKeyFromTable(iresidentkeyname, iresidentkeypwd, &ownkey);
   // If the key is actually not found, then the keys could be generated
   couldgeneratekeysflag = (errval == KEY_OPEN_ERROR__KEY_NOT_FOUND);
   tryToGetKeyErrorCheck(iresidentkeyname, errval);
}

/** The destructor for the key table view. */
KeyTableView::~KeyTableView()
{
   if (tablename) free(tablename);
   tablename = NULL;
   if (tableWIPpwd) free(tableWIPpwd);
   tableWIPpwd = NULL;
   if (residentkeyname) free(residentkeyname);
   residentkeyname = NULL;
   if (ownkey) delete ownkey;
   ownkey = NULL;
}

/** Count the number of keys in the key chain.
 * 
 * @param owner
 * The actor whose key chain is to be counted.
 * 
 * @return
 * The number of keys in the key chain. */
int KeyTableView::countKeyChainOfOwner(const char *owner)
{
   int keycount = 0;
   nkeydb->sqlGetSingleIntf(&keycount,
      "select count(keyname) from '%s' where owner='%s';", tablename, owner);
   return keycount;
}

/** Get the name of latest key in the specified chain.
 * 
 * @param owner
 * The owner of the key whose name is to be found.
 * 
 * @return
 * The name of the latest key. The returned pointer should be freed by the
 * caller. */
char *KeyTableView::getLatestKeyNameOfOwner(const char *owner)
{
   char *result = NULL;
   nkeydb->sqlGetSingleStringf(&result,
      "select keyname from '%s' where owner='%s' and keymark='%c';", tablename, owner, NewKeyDB::KEYMARK_CHAINLATEST);
   if (result) return result;
   nkeydb->sqlGetSingleStringf(&result,
      "select keyname from '%s' where owner='%s' and keymark='%c';", tablename, owner, NewKeyDB::KEYMARK_CHAINROOT);
   if (result) return result;
   int chaincount = countKeyChainOfOwner(owner);
   if (countKeyChainOfOwner(owner) > 0)
   {
      errprint("%s: there are %d keys owned by '%s' but none are directly latest - fixme broken keymark",
         tablename, chaincount, owner);
      std::vector<char *> *keynamedate_info =
         nkeydb->sqlScanAllTextf("select keyname,keymark from '%s' where owner='%s';", tablename, owner);
      if ((keynamedate_info == NULL) || (keynamedate_info->size() == 0))
      {
         errprint("%s: could not get keys owned by '%s', possible corruption", tablename, owner);
         if (keynamedate_info) delete keynamedate_info;
         return NULL;
      }
      apr_time_t minimum = 0;
      int chosenslot = -1;
      apr_time_t timet = apr_time_now();
      for (int i = 0; i < chaincount; i++)
      {
         apr_time_t timebefore = apr_date_parse_rfc(keynamedate_info->at(i*2 + 1));
         if ((timet - timebefore) < minimum)
         {
            minimum = (timet - timebefore);
            chosenslot = i;
         }
      }
      if (chosenslot == -1)
      {
         errprint("%s: corrupted or broken dates in key chain '%s'", tablename, owner);
         StringUtils::destroyStringList(keynamedate_info);
         return NULL;
      }
      errprint("%s: found latest key owned by '%s' dated '%s': is latest",
         tablename, owner, keynamedate_info->at(chosenslot*2 + 1));
      char *namedup = strdup(keynamedate_info->at(chosenslot*2 + 0));
      StringUtils::destroyStringList(keynamedate_info);
      return namedup;
   }
   errprint("%s: no keys found with owner '%s'", tablename, owner);
   return NULL;
}

/** Get the mark of the key with the specified name.
 * 
 * @param keyname
 * The name of the key whose mark is to be inspected.
 * 
 * @return
 * The character corresponding to an entry in NewKeyDB::keymarkstype. */
char KeyTableView::getMarkOfKey(const char *keyname)
{
   char *result = NULL;
   nkeydb->sqlGetSingleStringf(&result,
      "select keymark from '%s' where keyname='%s';", tablename, keyname);
   if (result == NULL) return '\0';
   char retval = result[0];
   free(result);
   return retval;
}

/** Set the mark of the key with the specified name (unsafe as it requires
 * the database mutex, NewKeyDB::dbmutex to be locked by the calling thread
 * for thread-safety).
 * 
 * @param keyname
 * The name of the key whose mark is to be replaced.
 * 
 * @param newmark
 * The mark that is to replace the mark currently on the key. */
void KeyTableView::setMarkOfKey_unsafe(const char *keyname, char newmark)
{
   if ((keyname == NULL) || (strlen(keyname) == 0))
   {
      errprint("name of key given should not be %s string", (keyname) ? "an empty" : "a NULL");
      return;
   }
   nkeydb->sqlInstantExecf_unsafe("update '%s' set keymark='%c' where keyname='%s';", tablename, newmark, keyname);
}

/** Set the expiry mark of the key with the specified name (unsafe as it
 * requires the database mutex, NewKeyDB::dbmutex to be locked by the
 * calling thread for thread-safety).
 * 
 * @param keyname
 * The name of the key which is to be deprecated. */
void KeyTableView::setKeyToExpire_unsafe(const char *keyname)
{
   if ((keyname == NULL) || (strlen(keyname) == 0))
   {
      errprint("name of key given should not be %s string", (keyname) ? "an empty" : "a NULL");
      return;
   }
   nkeydb->sqlInstantExecf_unsafe("update '%s' set keymark='%c' where keyname='%s';", tablename, NewKeyDB::EXPIRYMARK_DELETEIN3, keyname);
}

/** Retry loading the key for the main actor. This is to be called after a
 * key generation step.
 * 
 * @param ownpwd
 * The password required to open the main key. */
void KeyTableView::retryGettingOwnKeys(const char *ownpwd)
{
   if (couldgeneratekeysflag)
   {
      int errval = tryToGetKeyFromTable(residentkeyname, ownpwd, &ownkey);
      if (errval != KEY_OPEN_ERROR__KEY_NOT_FOUND)
      {
         if (errval != KEY_OPEN_ERROR__NO_ERROR)
            tryToGetKeyErrorCheck(residentkeyname, errval);
         couldgeneratekeysflag = false;
      }
   }
}

/** Insert a key into this table. This function is thread-safe.
 * 
 * @param name
 * The name of the key.
 * 
 * @param pwd
 * The password to use to encipher the key before insertion.
 * 
 * @param mark
 * The mark to put on the key.
 * 
 * @param owner
 * The owner of the key.
 * 
 * @param key
 * The key to insert into the table.
 * 
 * @return
 * The return value is one of KeyTableView::keyinserterror.
 * The error can be KeyTableView::tryToInsertKeyErrorCheck. */
int KeyTableView::tryToInsertKeyIntoTable(const char *name, const char *pwd, char mark, const char *owner, Key *key)
{
   return tryToInsertKeyIntoTable_tsafe(name, pwd, mark, owner, key, true);
}

/** Insert a key into this table.
 * 
 * @param name
 * The name of the key.
 * 
 * @param pwd
 * The password to use to encipher the key before insertion.
 * 
 * @param mark
 * The mark to put on the key.
 * 
 * @param owner
 * The owner of the key.
 * 
 * @param key
 * The key to insert into the table.
 * 
 * @param uselock
 * The flag that needs to be set to true to automatically lock the
 * NewKeyDB::dbmutex to access the database in a thread-safe manner.
 * 
 * @return
 * The return value is one of KeyTableView::keyinserterror.
 * The error can be made human-readable by calling the function
 * KeyTableView::tryToInsertKeyErrorCheck. Returns zero on success. */
int KeyTableView::tryToInsertKeyIntoTable_tsafe(const char *name, const char *pwd, char mark, const char *owner, Key *key, int uselock)
{
   char timebuf[APR_RFC822_DATE_LEN + 1];
   apr_status_t status;
   apr_time_t timet = apr_time_now();
   status = apr_rfc822_date(timebuf, timet);
   CheckAPRError(status);
   
   int errval = KEY_INSERT_ERROR__NO_ERROR;
   if ((name == NULL) || (strlen(name) == 0)) return KEY_INSERT_ERROR__INVALID_KEY_NAME;
   char *namedup = strdup(name);
   StringUtils::aggressiveStringSanitiser(namedup);
   if (strcmp(namedup, name) != 0) { free(namedup); return KEY_INSERT_ERROR__INVALID_KEY_NAME; }
   free(namedup);
   if (key == NULL) return KEY_INSERT_ERROR__INVALID_KEY_SRC;
   int retval = 0;
   char *textdata = NULL;
   if ((pwd == NULL) || (strlen(pwd) == 0))
   {
      retval = key->exportKeyToASCII(&textdata);
   } else
   {
      retval = key->exportKeyToASCIIWithPassword(&textdata, pwd);
   }
   if (retval != 0) return KEY_INSERT_ERROR__EXPORT_ERROR;
   char *nowner = (owner == NULL) ? strdup(owner) : strdup(name);
   if (uselock)
   {
      nkeydb->sqlInstantExecf("insert into '%s' "
         "(keyname, keytype, keydata, owner, latest, usedrefcount, existsrefcount, rfc822date) values "
         "('%s', '%s', '%s', '%s', '%s', '%c', '0', '0', '%s');",
         tablename, name, key->getKeyTypeAbbrName(), textdata, nowner, mark, timebuf);
   } else
   {
      nkeydb->sqlInstantExecf_unsafe("insert into '%s' "
         "(keyname, keytype, keydata, owner, latest, usedrefcount, existsrefcount, rfc822date) values "
         "('%s', '%s', '%s', '%s', '%s', '%c', '0', '0', '%s');",
         tablename, name, key->getKeyTypeAbbrName(), textdata, nowner, mark, timebuf);
   }
   free(nowner);
   return errval;
}

/** Get a key from the table.
 * 
 * @param name
 * The name of the key to find.
 * 
 * @param pwd
 * The password that could be required if the key is encrypted.
 * 
 * @param[out] key
 * The pointer to the place to put the pointer to the Key. The Key object must
 * be deleted by the caller.
 * 
 * @return
 * Returns a value in KeyTableView::keyopenerror. Returns zero on success.
 * The error can be made human-readable by calling the function
 * KeyTableView::tryToOpenKeyErrorCheck.*/
int KeyTableView::tryToGetKeyFromTable(const char *name, const char *pwd, Key **key)
{
   int errval = KEY_OPEN_ERROR__NO_ERROR;
   std::vector<char *> *key_strlist =
      nkeydb->sqlScanRowTextf("select keytype,keydata from '%s' where keyname = '%s';", tablename, name);
   if ((name == NULL) || (strlen(name) == 0)) return KEY_OPEN_ERROR__INVALID_KEY_NAME;
   char *namedup = strdup(name);
   StringUtils::aggressiveStringSanitiser(namedup);
   if (strcmp(namedup, name) != 0) return KEY_OPEN_ERROR__INVALID_KEY_NAME;
   free(namedup);
   if (key == NULL) return KEY_OPEN_ERROR__INVALID_KEY_DST;
   if ((key_strlist == NULL) || (key_strlist->size() == 0))
   {
      if (key_strlist) delete key_strlist;
      return KEY_OPEN_ERROR__KEY_NOT_FOUND;
   }
   if (key_strlist->size() != 2)
   {
      StringUtils::destroyStringList(key_strlist);
      return KEY_OPEN_ERROR__FAILED_SANITY;
   }
   if (strcmp(key_strlist->at(0), "PrivateRSA") == 0)
   {
      RSAPrivateKey *prvkey = new RSAPrivateKey(name);
      if (pwd)
         errval = prvkey->importKeyFromASCIIWithPassword(key_strlist->at(1), pwd, name);
      else
         errval = prvkey->importKeyFromASCII(key_strlist->at(1), name);
      if (errval)
      {
         delete prvkey;
         return KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
      }
      *key = (Key *)prvkey;
   } else
   if (strcmp(key_strlist->at(0), "PublicRSA") == 0)
   {
      RSAPublicKey *pubkey = new RSAPublicKey(name);
      if (pwd)
         errval = pubkey->importKeyFromASCIIWithPassword(key_strlist->at(1), pwd, name);
      else
         errval = pubkey->importKeyFromASCII(key_strlist->at(1), name);
      if (errval)
      {
         delete pubkey;
         return KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
      }
      *key = (Key *)pubkey;
   } else
   if (strcmp(key_strlist->at(0), "SymmAES") == 0)
   {
      AESSymmetricKey *symkey = new AESSymmetricKey(name);
      if (pwd)
         errval = symkey->importKeyFromASCIIWithPassword(key_strlist->at(1), pwd, name);
      else
         errval = symkey->importKeyFromASCII(key_strlist->at(1), name);
      if (errval)
      {
         delete symkey;
         return KEY_OPEN_ERROR__KEY_IMPORT_FAILED;
      }
      *key = (Key *)symkey;
   }
   if (*key)
      return KEY_OPEN_ERROR__NO_ERROR;
   else
      return KEY_OPEN_ERROR__KEY_NULL;
}

/** Generates an error with a description for a non-zero return value of
 * KeyTableView::tryToGetKeyFromTable.
 * 
 * @param keyname
 * Key name associated with the error to give context to the error message.
 * 
 * @param terrno
 * The error number returned from KeyTableView::tryToGetKeyFromTable. */
void KeyTableView::tryToGetKeyErrorCheck(const char *keyname, int terrno)
{
   switch (terrno)
   {
      case KEY_OPEN_ERROR__NO_ERROR:
         break;
      case KEY_OPEN_ERROR__INVALID_KEY_NAME:
         errprint("(table: %s, key: %s) key name to be found was invalid", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__INVALID_KEY_DST:
         errprint("(table: %s, key: %s) invalid key destination pointer", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__KEY_NOT_FOUND:
         errprint("(table: %s, key: %s) key not found in database", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__FAILED_SANITY:
         errprint("(table: %s, key: %s) key importing failed basic sanity checks", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__KEY_IMPORT_FAILED:
         errprint("(table: %s, key: %s) key could not be recovered from data in database", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_OPEN_ERROR__KEY_NULL:
         errprint("(table: %s, key: %s) key could not be recovered from data in database: key was NULL", tablename, (keyname) ? keyname : "(null)"); break;
      default:
         errprint("(table: %s, key: %s) unknown get key error from key database", tablename, (keyname) ? keyname : "(null)"); break;
   }
}

/** Generates an error with a description for a non-zero return value of
 * KeyTableView::tryToInsertKeyIntoTable.
 * 
 * @param keyname
 * Key name associated with the error to give context to the error message.
 * 
 * @param terrno
 * The error number returned from KeyTableView::tryToInsertKeyIntoTable. */
void KeyTableView::tryToInsertKeyErrorCheck(const char *keyname, int terrno)
{
   switch (terrno)
   {
      case KEY_INSERT_ERROR__NO_ERROR:
         break;
      case KEY_INSERT_ERROR__INVALID_KEY_NAME:
         errprint("(table: %s, key: %s) key name to be found was invalid", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_INSERT_ERROR__INVALID_KEY_SRC:
         errprint("(table: %s, key: %s) invalid key source pointer", tablename, (keyname) ? keyname : "(null)"); break;
      case KEY_INSERT_ERROR__EXPORT_ERROR:
         errprint("(table: %s, key: %s) key export to text failed", tablename, (keyname) ? keyname : "(null)"); break;
      default:
         errprint("(table: %s, key: %s) unknown insert key error from key database", tablename, (keyname) ? keyname : "(null)"); break;
   }         
}
